/**
 * 
 */
package org.hackystat.projecttrajectory.resource;

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.hackystat.projecttrajectory.resource.estimate.jaxb.EstimateIndex;
import org.hackystat.projecttrajectory.resource.estimate.jaxb.ProjectEstimate;
import org.hackystat.projecttrajectory.resource.summary.jaxb.ProjectSummary;
import org.hackystat.projecttrajectory.resource.trajectory.jaxb.ProjectTrajectory;
import org.w3c.dom.Document;

/**
 * Singleton that provides methods to convert resources to Strings and vice versa.
 * @author Cam Moore
 */
public final class ResourceFactory {
  private JAXBContext summaryJAXB;
  private JAXBContext trajectoryJAXB;
  private JAXBContext estimateJAXB;
  
  private static ResourceFactory instance;
  /**
   * Constructs a ResourceFactory.
   * @throws JAXBException if there is a problem creating the contexts.
   */
  private ResourceFactory() throws JAXBException {
  summaryJAXB = JAXBContext
  .newInstance(org.hackystat.projecttrajectory.resource.summary.jaxb.ObjectFactory.class);
  trajectoryJAXB = JAXBContext
  .newInstance(org.hackystat.projecttrajectory.resource.trajectory.jaxb.ObjectFactory.class);
  estimateJAXB = JAXBContext
  .newInstance(org.hackystat.projecttrajectory.resource.estimate.jaxb.ObjectFactory.class);
  }

  /**
   * @return The singleton instance.
   * @throws JAXBException if there is a problem constructing the instance.
   */
  public static synchronized ResourceFactory getInstance() throws JAXBException {
    if (instance == null) {
      instance = new ResourceFactory();
    }
    return instance;
  }
  
  /**
   * @param xmlString the String representation of the EstimateIndex.
   * @return The EstimateIndex instance from the xmlString representation.
   * @throws JAXBException If there is a problem unmarshalling the xmlString.
   */
  public EstimateIndex makeEstimateIndex(String xmlString) throws JAXBException {
    Unmarshaller unmarshaller = estimateJAXB.createUnmarshaller();
    return (EstimateIndex) unmarshaller.unmarshal(new StringReader(xmlString));
  }
  
  /**
   * @param xmlString the String representation of the EstimateIndex.
   * @return The EstimateIndex instance from the xmlString representation.
   * @throws JAXBException If there is a problem unmarshalling the xmlString.
   */
  public ProjectEstimate makeProjectEstimate(String xmlString) throws JAXBException {
    Unmarshaller unmarshaller = estimateJAXB.createUnmarshaller();
    return (ProjectEstimate) unmarshaller.unmarshal(new StringReader(xmlString));
  }

  /**
   * @param xmlString the String representing the ProjectSummary.
   * @return The Projectsummary instance from the xmlString.
   * @throws JAXBException if there is a problem unmarshalling the xmlString.
   */
  public ProjectSummary makeProjectSummary(String xmlString) throws JAXBException {
    Unmarshaller unmarshaller = summaryJAXB.createUnmarshaller();
    return (ProjectSummary) unmarshaller.unmarshal(new StringReader(xmlString));    
  }
  
  /**
   * @param xmlString the String representing the ProjectTrajectory.
   * @return The ProjectTrajectory instance from the xmlString.
   * @throws JAXBException if there is a problem unmarshalling the xmlString.
   */
  public ProjectTrajectory makeProjectTrajectory(String xmlString) throws JAXBException {
    Unmarshaller unmarshaller = trajectoryJAXB.createUnmarshaller();
    return (ProjectTrajectory) unmarshaller.unmarshal(new StringReader(xmlString));    
  }

  /**
   * Converts an EstimateIndex into its XML String representation.
   * @param index the EstimateIndex to convert.
   * @return The XML String representation of the EstimateIndex.
   * @throws Exception if there is a problem.
   */
  public String makeXml(EstimateIndex index) throws Exception {
    return convertJAXB(estimateJAXB, index);
  }
  
  /**
   * Converts an ProjectEstimate into its XML String representation.
   * @param estimate the ProjectEstimate to convert.
   * @return The XML String representation of the ProjectEstimate.
   * @throws Exception if there is a problem.
   */
  public String makeXml(ProjectEstimate estimate) throws Exception {
    return convertJAXB(estimateJAXB, estimate);
  }


  /**
   * Converts an ProjectSummary into its XML String representation.
   * @param index the ProjectSummary to convert.
   * @return The XML String representation of the ProjectSummary.
   * @throws Exception if there is a problem.
   */
  public String makeXml(ProjectSummary index) throws Exception {
    return convertJAXB(summaryJAXB, index);
  }

  /**
   * Converts an ProjectTrajectory into its XML String representation.
   * @param index the ProjectTrajectory to convert.
   * @return The XML String representation of the ProjectTrajectory.
   * @throws Exception if there is a problem.
   */
  public String makeXml(ProjectTrajectory index) throws Exception {
    return convertJAXB(trajectoryJAXB, index);
  }

  /**
   * Converts the given JAXB object into an XML String representation using the
   * JAXBContext.
   * @param context the JAXBContext.
   * @param obj the JAXB object instance.
   * @return The  XML String representation of the JAXB instance.
   * @throws Exception if there is a problem in the conversion.
   */
  private String convertJAXB(JAXBContext context, Object obj) throws Exception {
    Marshaller marshaller = context.createMarshaller();
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(obj, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    // Now remove the processing instruction.  This approach seems like a total hack.
    xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
    return xmlString;
  }
}
